package com.xrui.hbase.util;

import org.apache.hadoop.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.IOException;

/**
 * Utilities to work with ReferenceCountable resources.
 */
public final class ResourceUtils {
    private static final Logger LOG = LoggerFactory.getLogger(ResourceUtils.class);

    /**
     * Utility class cannot be instantiated.
     */
    private ResourceUtils() {
    }

    /**
     * Closes the specified resource, logging and swallowing I/O errors if needed.
     *
     * @param resource Close this resource.
     */
    public static void closeOrLog(Closeable resource) {
        if (resource == null) {
            return;
        }
        try {
            resource.close();
        } catch (IOException ioe) {
            LOG.warn("I/O error while closing resource '{}':\n{}",
                resource, StringUtils.stringifyException(ioe));
        }
    }

    /**
     * Releases the specified resource, logging and swallowing I/O errors if needed.
     *
     * @param resource Release this resource.
     * @param <T>      Type of the resource to release.
     */
    public static <T extends ReferenceCountable<T>> void releaseOrLog(
        ReferenceCountable<T> resource) {
        if (resource == null) {
            return;
        }
        try {
            resource.release();
        } catch (IOException ioe) {
            LOG.warn("I/O error while releasing resource '{}':\n{}",
                resource, StringUtils.stringifyException(ioe));
        }
    }

    /**
     * Exception representing an exception thrown while handling another exception and the original
     * exception.
     */
    public static final class CompoundException extends Exception {

        private final String mMessage;
        private final Exception mFirstException;
        private final Exception mSecondException;

        /**
         * Initialize a new CompoundException.
         *
         * @param message         error message for this exception.
         * @param firstException  chronologically first exception.
         * @param secondException exception which occurred while handling the first exception.
         */
        public CompoundException(
            final String message,
            final Exception firstException,
            final Exception secondException
        ) {
            mMessage = String.format("%s: first exception: %s second exception: %s",
                message, firstException.getMessage(), secondException.getMessage());
            mFirstException = firstException;
            mSecondException = secondException;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getMessage() {
            return mMessage;
        }

        /**
         * Get the first exception from this compound exception.
         *
         * @return the first exception from this compound exception.
         */
        public Exception getFirstException() {
            return mFirstException;
        }

        /**
         * Get the second exception from this compound exception.
         *
         * @return the second exception from this compound exception.
         */
        public Exception getSecondException() {
            return mSecondException;
        }
    }

    /**
     * Perform two actions with a resource. Typically the second action will be cleanup.
     *
     * @param <RESOURCE> Type of the resource.
     * @param <RETURN>   Type of the return value of the first action.
     */
    public abstract static class DoAnd<RESOURCE, RETURN> {

        /**
         * Open the resource.
         *
         * @return the resource with which to perform both actions.
         * @throws Exception in case of an error opening the resource.
         */
        protected abstract RESOURCE openResource() throws Exception;

        /**
         * Perform the first action with the resource.
         *
         * @param resource the resource with which to perform the action.
         * @return the result of the action.
         * @throws Exception in case of an error performing the action.
         */
        protected abstract RETURN run(RESOURCE resource) throws Exception;

        /**
         * Perform the second action with the resource. Typically this will be cleanup.
         *
         * @param resource the resource with which to perform the action.
         * @throws Exception in case of an error performing the action.
         */
        protected abstract void after(RESOURCE resource) throws Exception;

        /**
         * Open the resource and perform both actions without losing any exceptions.
         *
         * @return the return value of {@link #run(Object)}.
         * @throws Exception in case of an error opening the resource or performing the actions.
         */
        public final RETURN eval() throws Exception {
            Exception exception = null;
            RESOURCE resource = null;
            try {
                resource = openResource();
                return run(resource);
            } catch (Exception e) {
                exception = e;
                throw e;
            } finally {
                try {
                    if (resource != null) {
                        after(resource);
                    }
                } catch (Exception e) {
                    if (exception != null) {
                        throw new CompoundException("Exception was throw while cleaning up resources after "
                            + "another exception was thrown.", exception, e);
                    } else {
                        throw e;
                    }
                }
            }
        }
    }

    /**
     * Perform an action with a {@link Closeable} resource and then close the resource.
     *
     * @param <RESOURCE> Type of the resource.
     * @param <RET>      Return type of the action.
     */
    public abstract static class DoAndClose<RESOURCE extends Closeable, RET>
        extends DoAnd<RESOURCE, RET> {
        /**
         * {@inheritDoc}
         */
        @Override
        public final void after(
            final RESOURCE closeable
        ) throws IOException {
            closeable.close();
        }
    }

    /**
     * Perform an action with a {@link com.tr.ts.hbase.util.ReferenceCountable} resource and then
     * release the resource.
     *
     * @param <RESOURCE> Type of the resource.
     * @param <RET>      Return type of the action.
     */
    public abstract static class DoAndRelease<RESOURCE extends ReferenceCountable<RESOURCE>, RET>
        extends DoAnd<RESOURCE, RET> {
        /**
         * {@inheritDoc}
         */
        @Override
        public final void after(
            final RESOURCE releasable
        ) throws IOException {
            releasable.release();
        }
    }

//    /**
//     * Perform an action with a Kiji instance and then release the instance.
//     *
//     * @param <RET> Return type of the action.
//     */
//    public abstract static class WithHBase<RET> extends DoAndRelease<Database, RET> {
//        private final HBaseURI mUri;
//
//        /**
//         * Initialize a new WithHBase.
//         *
//         * @param uri HBaseURI of the Database instance with which to perform the action.
//         */
//        public WithHBase(final HBaseURI uri) {
//            mUri = uri;
//        }
//
//        /**
//         * {@inheritDoc}
//         */
//        @Override
//        public final Database openResource() throws IOException {
//            return Database.Factory.open(mUri);
//        }
//    }
//
//    /**
//     * Perform an action with a DatabaseTable and then release the table.
//     *
//     * @param <RET> Return type of the action.
//     */
//    public abstract static class WithTable<RET> extends DoAndRelease<DatabaseTable, RET> {
//        private final HBaseURI mTableURI;
//        private final Database mHBase;
//        private final String mTableName;
//
//        /**
//         * Initialize a new WithTable.
//         *
//         * @param tableURI HBaseURI of the table with which to perform an action.
//         */
//        public WithTable(final HBaseURI tableURI) {
//            mTableURI = tableURI;
//            mHBase = null;
//            mTableName = null;
//        }
//
//        /**
//         * Initialize a new WithTable.
//         *
//         * @param hbase     Database instance from which to create the table.
//         * @param tableName Name of the table.
//         */
//        public WithTable(
//            final Database hbase,
//            final String tableName
//        ) {
//            mTableURI = null;
//            mHBase = hbase;
//            mTableName = tableName;
//        }
//
//        /**
//         * {@inheritDoc}
//         */
//        @Override
//        public final DatabaseTable openResource() throws Exception {
//            if (null != mTableURI) {
//                return new GetTable(mTableURI).eval();
//            } else {
//                return mHBase.openTable(mTableName);
//            }
//        }
//
//        /**
//         * WithHBase that returns a DatabaseTable from a HBaseURI.
//         */
//        private final class GetTable extends WithHBase<DatabaseTable> {
//
//            /**
//             * Initialize a new GetTable.
//             *
//             * @param tableUri HBaseURI of the table to return.
//             */
//            public GetTable(
//                final HBaseURI tableUri
//            ) {
//                super(tableUri);
//            }
//
//            /**
//             * {@inheritDoc}
//             */
//            @Override
//            protected DatabaseTable run(
//                final Database hbase
//            ) throws Exception {
//                return hbase.openTable(mTableURI.getTable());
//            }
//        }
//    }
//
//    /**
//     * Perform an action with a TableWriter and then close the writer.
//     *
//     * @param <RET> Return value of the action.
//     */
//    public abstract static class WithTableWriter<RET> extends DoAndClose<TableWriter, RET> {
//
//        // Exactly one of these fields must be non-null.
//        private final HBaseURI mTableURI;
//        private final DatabaseTable mTable;
//
//        /**
//         * Initialize a new WithTableWriter.
//         *
//         * @param tableURI HBaseURI of the table from which to create the writer.
//         */
//        public WithTableWriter(
//            final HBaseURI tableURI
//        ) {
//            mTableURI = tableURI;
//            mTable = null;
//        }
//
//        /**
//         * Initialize a new WithTableWriter.
//         *
//         * @param table DatabaseTable from which to create the writer.
//         */
//        public WithTableWriter(
//            final DatabaseTable table
//        ) {
//            mTableURI = null;
//            mTable = table;
//        }
//
//        /**
//         * {@inheritDoc}
//         */
//        @Override
//        public final TableWriter openResource() throws Exception {
//            if (null != mTableURI) {
//                final WithTable<TableWriter> wkt = new WithTable<TableWriter>(mTableURI) {
//                    @Override
//                    public TableWriter run(final DatabaseTable table) throws Exception {
//                        return table.openTableWriter();
//                    }
//                };
//                return wkt.eval();
//            } else {
//                return mTable.openTableWriter();
//            }
//        }
//    }
//
//    /**
//     * Perform an action with a TableReader and then close the reader.
//     *
//     * @param <RET> type of the return value of the action.
//     */
//    public abstract static class WithTableReader<RET> extends DoAndClose<TableReader, RET> {
//
//        // Exactly one of these fields must be non-null.
//        private final HBaseURI mTableURI;
//        private final DatabaseTable mTable;
//        private final TableReaderPool mPool;
//
//        /**
//         * Initialize a new WithTableReader.
//         *
//         * @param table DatabaseTable from which to create the reader.
//         */
//        public WithTableReader(
//            final DatabaseTable table
//        ) {
//            mTableURI = null;
//            mTable = table;
//            mPool = null;
//        }
//
//        /**
//         * Initialize a new WithTableReader.
//         *
//         * @param tableURI HBaseURI of the table from which to create the reader.
//         */
//        public WithTableReader(
//            final HBaseURI tableURI
//        ) {
//            mTableURI = tableURI;
//            mTable = null;
//            mPool = null;
//        }
//
//        /**
//         * Initialize a new WithTableReader.
//         *
//         * @param pool TableReaderPool from which to create the reader with which to perform an action.
//         */
//        public WithTableReader(
//            final TableReaderPool pool
//        ) {
//            mTableURI = null;
//            mTable = null;
//            mPool = pool;
//        }
//
//        /**
//         * {@inheritDoc}
//         */
//        @Override
//        public final TableReader openResource() throws Exception {
//            if (null != mTableURI) {
//                final WithTable<TableReader> wkt = new WithTable<TableReader>(mTableURI) {
//                    @Override
//                    public TableReader run(final DatabaseTable table) throws Exception {
//                        return table.openTableReader();
//                    }
//                };
//                return wkt.eval();
//            } else if (null != mTable) {
//                return mTable.openTableReader();
//            } else {
//                return mPool.borrowObject();
//            }
//        }
//    }
}
